Required R packages

The diprate package is available from GitHub here: dipDRC

library(diprate)
options(stringsAsFactors=FALSE)

Static data

static <- read.csv("../data/from_CW_via_Slack_20210507/StaticDF.csv", row.names=1)
static <- static[order(static$Culture_Type,static$Cell_Line),]
static[static$Cell_Conc==0,"Cell_Conc"] <- 1
cell_lines <- unique(static$Cell_Line)

Outlier value in CORL279

static <- static[!(static$Cell_Line=="CORL279" & static$RLU < 10),]

Code for reproducing figures

Correlation between luminescence and cell count

par(mfrow=c(2,5))
linear_models <- lapply(cell_lines, function(cl) {
        dat <- static[static$Cell_Line==cl,]
        culture_type <- unique(dat$Culture_Type)
        m <- lm(log2(RLU) ~ log2(Cell_Conc), data = dat)
        plot(log2(RLU) ~ log2(Cell_Conc), data=dat, main=paste0(cl," (",culture_type,")"), xlim=c(0,14), ylim=c(7,18))
        abline(m, col="blue")
        return(m)
    })

names(linear_models) <- cell_lines

Need 2-part function to accommodate minimum values

Assuming first part of data is at some minimum value and not associated with any actual cell count (lower limit of detection), which would result in slope=0 (values are constant until some minimum number of cells is achieved).

lagLine <- function (x, lower, slope, br = 64) sapply(x, function(z) ifelse(z <= br, lower, lower + (z-br) * slope))

fitLagLin <- function(x, y, start_list=list(lower=5, slope=1, br=1)) 
    nls(y ~ lagLine(x=x, lower, slope, br),
        start = start_list,
        algorithm="port",
        control=nls.control(maxiter=500)
        )

Fit the lag-linear model to data

par(mfrow=c(2,5))
lag_linear_models <- lapply(cell_lines, function(cl) {
    m <- tryCatch({
        dat <- static[static$Cell_Line==cl,]
        culture_type <- unique(dat$Culture_Type)
        m <- fitLagLin(log2(dat$Cell_Conc), log2(dat$RLU))
    }, error=function(e) { return(e) })
    # if(culture_type == "Adherent") 
    # {
    #     xr <- c(4,12)
    # } else {
    #     xr <- c(6,14)
    # }
    xr <- c(0,14)
    plot(log2(RLU) ~ log2(Cell_Conc), data=dat, main=paste0(cl," (",culture_type,")"), xlim=xr, ylim=c(7,18))
    if(class(m)[1] != "nls")
    {
        m <- lm(log2(RLU) ~ log2(Cell_Conc), data=dat[dat$Cell_Conc>1,])
        abline(m, col="blue", lwd=2)
    } else {
        curve(from=0.5,to=18, lagLine(x, lower=coef(m)['lower'], 
                                      slope=coef(m)['slope'], 
                                      br=coef(m)['br']), 
              col="blue", lwd=2, add=TRUE)
    }
    # text(12, 9, paste("Adj R2 ="))
    return(m)
})

names(lag_linear_models) <- cell_lines

Try eliminating controls

Assume all luminescence values with cells produce detectable signal.

par(mfrow=c(2,5))
linear_models <- lapply(cell_lines, function(cl) {
        dat <- static[static$Cell_Line==cl & static$Cell_Conc >1,]
        culture_type <- unique(dat$Culture_Type)
        m <- lm(log2(RLU) ~ log2(Cell_Conc), data = dat)
        plot(log2(RLU) ~ log2(Cell_Conc), data=dat, main=paste0(cl," (",culture_type,")"), xlim=c(0,14), ylim=c(7,18))
        abline(m, col="blue")
        return(m)
    })

names(linear_models) <- cell_lines

Compare to linear models

Assuming the lowest number of cells is above the threshold of detection, will remove the no cells control and fit remaining data.

dat <- static[static$Cell_Conc > 1,]
m2 <- lme4::lmList(log2(RLU) ~ log2(Cell_Conc) | Cell_Line, data=dat)
f2 <- coef(m2)
r2 <- unlist(summary(m2)$adj.r.squared)
par(mfrow=c(2,5))
temp <- lapply(cell_lines, function(cl) {
    dtp <- dat[dat$Cell_Line==cl,]
    culture_type <- unique(dtp[dtp$Cell_Line==cl,'Culture_Type'])
    if(culture_type == "Adherent") 
    {
        xr <- c(5,12)
    } else {
        xr <- c(7,14)
    }
    plot(log2(RLU) ~ log2(Cell_Conc), 
         data=dtp, 
         main=paste0(cl," (",culture_type,")"), 
         xlim=xr, ylim=c(7,18))
    abline(m2[[cl]], col="blue", lwd=2)
    text(xr[1]+0.25, 17, pos=4, paste("slope =",signif(f2[cl,2],3)))
    text(xr[1]+0.25, 16, pos=4, expression(R^2))
    text(xr[1]+1, 16, pos=4, paste("=", signif(r2[cl],3)))
})

fitLagLin1 <- function(x, y, start_list=list(lower=5, br=1)) 
    nls(y ~ lagLine(x=x, lower, slope=1, br),
        start = start_list,
        algorithm="port",
        control=nls.control(maxiter=500)
        )

par(mfrow=c(2,5))
lag_linear1_models <- lapply(cell_lines, function(cl) {
    m <- tryCatch({
        dat <- static[static$Cell_Line==cl,]
        culture_type <- unique(dat$Culture_Type)
        m <- fitLagLin1(log2(dat$Cell_Conc), log2(dat$RLU))
    }, error=function(e) { return(e) })
    # if(culture_type == "Adherent") 
    # {
    #     xr <- c(4,12)
    # } else {
    #     xr <- c(6,14)
    # }
    xr <- c(0,14)
    plot(log2(RLU) ~ log2(Cell_Conc), data=dat, main=paste0(cl," (",culture_type,")"), xlim=xr, ylim=c(7,18))
    if(class(m)[1] != "nls")
    {
        m <- lm(log2(RLU) ~ log2(Cell_Conc), data=dat[dat$Cell_Conc>1,])
        abline(m, col="blue", lwd=2)
    } else {
        curve(from=0.5,to=18, lagLine(x, lower=coef(m)['lower'], 
                                      slope=1, 
                                      br=coef(m)['br']), 
              col="blue", lwd=2, add=TRUE)
    }
    # text(12, 9, paste("Adj R2 ="))
    return(m)
})

names(lag_linear1_models) <- cell_lines

Combined cell count & lum data

lcc <- read.csv('../data/from_CW_via_Slack_20210507/20201216_Lum_CellCounts_Thunor.csv', row.names=1, as.is=TRUE)
lcc <- lcc[,-1]
lcc$uid <- paste(lcc$upid,lcc$well,sep="_")
lcc <- lcc[order(lcc$uid,lcc$time),]
lcc <- lcc[lcc$time <= 96,]
lcc_cell_lines <- unique(lcc$cell.line)
ctrls <- lapply(lcc_cell_lines, function(cl) lcc[lcc$cell.line==cl & lcc$drug1.conc==0,])
names(ctrls) <- lcc_cell_lines

Cell counts

par(mfrow=c(2,2))
invisible(lapply(names(ctrls), function(n) do.call(plotGC, 
        append(getGCargs(ctrls[[n]], dat.col=c("time","Cell_Count","uid")),list(main=n, leg=FALSE)))))

Luminscence

par(mfrow=c(2,2))
invisible(lapply(names(ctrls), function(n) do.call(plotGC, 
        append(getGCargs(ctrls[[n]], dat.col=c("time","RLU","uid")),list(main=n, leg=FALSE)))))

Sum of all control cell counts at each time point

sumc <- do.call(rbind, lapply(lcc_cell_lines, function(cl) 
    {
        counts <- sapply(unique(ctrls[[cl]]$time), function(i) 
            sum(ctrls[[cl]][ctrls[[cl]]$time==i,"Cell_Count"]))
        times <- unique(ctrls[[cl]]$time)
        data.frame(cell.line=cl, time=times, cell.count=counts)
}))
sumc$popdubs <- log2norm(sumc$cell.count,sumc$cell.line)
sumc$cell.line <- as.character(sumc$cell.line)
# h1048_sumc <- data.frame(time=unique(h1048$time), cell.count=h1048_sumc)
# plot(log2(cell.count)-log2(cell.count)[1] ~ time, data=h1048_sumc, type="l", ylab="Population doublings")
par(mfrow=c(3,2))

invisible(lapply(lcc_cell_lines[lcc_cell_lines!="WM88"], function(cl)
{
    dtp <- ctrls[[cl]]
    invisible(do.call(plotGC, append(getGCargs(dtp, dat.col=c("time","Cell_Count","uid")),
                                     list(main=paste0(cl,", cell count"), leg=FALSE))))
    invisible(do.call(plotGC, append(getGCargs(dtp, dat.col=c("time","RLU","uid")),
                                     list(main=paste0(cl,", lum"), leg=FALSE, ylim=c(-1,3)))))
    lines(sumc[sumc$cell.line==cl,"time"], sumc[sumc$cell.line==cl,"popdubs"], lwd=3)
}))

lumo <- read.csv("../data/from_CW_via_Slack_20210507/050819_RTglowDF.csv")
lumo <- lumo[,-1]

lumo_cell_lines <- unique(lumo$cell.line)
lumo_ctrl_dat <- lapply(lumo_cell_lines, function(cl) lumo[lumo$cell.line==cl & lumo$drug1.conc==0 & lumo$drug1!="DMSO",])
names(lumo_ctrl_dat) <- lumo_cell_lines
par(mfrow=c(2,4))

invisible(lapply(lumo_cell_lines, function(cl)
{
    dtp <- lumo_ctrl_dat[[cl]]
    # plot(dtp$TotHour, dtp$RLU)
    
    invisible(do.call(plotGC, append(getGCargs(dtp, dat.col=c("TotHour","RLU","well"), arg.name = c("time", "cell.count", "ids")),
                                     list(main=cl, leg=FALSE))))
}))

corl279 <- lumo_ctrl_dat[['CORL279']]

do.call(plotGC, append(getGCargs(a, dat.col=c("TotHour","RLU","well"), arg.name = c("time", "cell.count", "ids")),
                                     list(main="CORL279", leg=FALSE)))
NULL

LS0tCnRpdGxlOiAiUmVhbC10aW1lIGx1bWluZXNjZW5jZSBlbmFibGVzIGVzdGltYXRpb24gb2YgZHJ1Zy1pbmR1Y2VkIHByb2xpZmVyYXRpb24gcmF0ZXMgaW4gYWRoZXJlbnQgYW5kIHN1c3BlbnNpb24gY2VsbCBsaW5lcyIKYXV0aG9yOiAiRGFycmVuIFR5c29uICYgQ2xheXRvbiBXYW5kaXNoaW4iCmRhdGU6ICIwNS8wOS8yMDIxIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBSZXF1aXJlZCBSIHBhY2thZ2VzClRoZSBgZGlwcmF0ZWAgcGFja2FnZSBpcyBhdmFpbGFibGUgZnJvbSBHaXRIdWIgaGVyZTogW2RpcERSQ10oaHR0cHM6Ly93d3cuZ2l0aHViLmNvbS9RdWxhYi1WVS9kaXBEUkMpCmBgYHtyIFNldHVwfQpsaWJyYXJ5KGRpcHJhdGUpCm9wdGlvbnMoc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKYGBgCgojIyBTdGF0aWMgZGF0YQpgYGB7ciBMb2FkIGRhdGF9CnN0YXRpYyA8LSByZWFkLmNzdigiLi4vZGF0YS9mcm9tX0NXX3ZpYV9TbGFja18yMDIxMDUwNy9TdGF0aWNERi5jc3YiLCByb3cubmFtZXM9MSkKc3RhdGljIDwtIHN0YXRpY1tvcmRlcihzdGF0aWMkQ3VsdHVyZV9UeXBlLHN0YXRpYyRDZWxsX0xpbmUpLF0Kc3RhdGljW3N0YXRpYyRDZWxsX0NvbmM9PTAsIkNlbGxfQ29uYyJdIDwtIDEKY2VsbF9saW5lcyA8LSB1bmlxdWUoc3RhdGljJENlbGxfTGluZSkKYGBgCiMjIyMgT3V0bGllciB2YWx1ZSBpbiBDT1JMMjc5CmBgYHtyfQpzdGF0aWMgPC0gc3RhdGljWyEoc3RhdGljJENlbGxfTGluZT09IkNPUkwyNzkiICYgc3RhdGljJFJMVSA8IDEwKSxdCmBgYAoKCiMjIENvZGUgZm9yIHJlcHJvZHVjaW5nIGZpZ3VyZXMKIyMjIENvcnJlbGF0aW9uIGJldHdlZW4gbHVtaW5lc2NlbmNlIGFuZCBjZWxsIGNvdW50CmBgYHtyIENlbGwgY291bnQgJiBsdW1pbmVzY2VuY2UsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTd9CnBhcihtZnJvdz1jKDIsNSkpCmxpbmVhcl9tb2RlbHMgPC0gbGFwcGx5KGNlbGxfbGluZXMsIGZ1bmN0aW9uKGNsKSB7CiAgICAgICAgZGF0IDwtIHN0YXRpY1tzdGF0aWMkQ2VsbF9MaW5lPT1jbCxdCiAgICAgICAgY3VsdHVyZV90eXBlIDwtIHVuaXF1ZShkYXQkQ3VsdHVyZV9UeXBlKQogICAgICAgIG0gPC0gbG0obG9nMihSTFUpIH4gbG9nMihDZWxsX0NvbmMpLCBkYXRhID0gZGF0KQogICAgICAgIHBsb3QobG9nMihSTFUpIH4gbG9nMihDZWxsX0NvbmMpLCBkYXRhPWRhdCwgbWFpbj1wYXN0ZTAoY2wsIiAoIixjdWx0dXJlX3R5cGUsIikiKSwgeGxpbT1jKDAsMTQpLCB5bGltPWMoNywxOCkpCiAgICAgICAgYWJsaW5lKG0sIGNvbD0iYmx1ZSIpCiAgICAgICAgcmV0dXJuKG0pCiAgICB9KQpuYW1lcyhsaW5lYXJfbW9kZWxzKSA8LSBjZWxsX2xpbmVzCmBgYAoKIyMjIyBOZWVkIDItcGFydCBmdW5jdGlvbiB0byBhY2NvbW1vZGF0ZSBtaW5pbXVtIHZhbHVlcwpBc3N1bWluZyBmaXJzdCBwYXJ0IG9mIGRhdGEgaXMgYXQgc29tZSBtaW5pbXVtIHZhbHVlIGFuZCBub3QgYXNzb2NpYXRlZCB3aXRoIGFueSBhY3R1YWwgY2VsbCBjb3VudCAobG93ZXIgbGltaXQgb2YgZGV0ZWN0aW9uKSwgd2hpY2ggd291bGQgcmVzdWx0IGluIGBzbG9wZT0wYCAodmFsdWVzIGFyZSBjb25zdGFudCB1bnRpbCBzb21lIG1pbmltdW0gbnVtYmVyIG9mIGNlbGxzIGlzIGFjaGlldmVkKS4KYGBge3IgTGFnLWxpbmVhciBmdW5jdGlvbn0KbGFnTGluZSA8LSBmdW5jdGlvbiAoeCwgbG93ZXIsIHNsb3BlLCBiciA9IDY0KSBzYXBwbHkoeCwgZnVuY3Rpb24oeikgaWZlbHNlKHogPD0gYnIsIGxvd2VyLCBsb3dlciArICh6LWJyKSAqIHNsb3BlKSkKCmZpdExhZ0xpbiA8LSBmdW5jdGlvbih4LCB5LCBzdGFydF9saXN0PWxpc3QobG93ZXI9NSwgc2xvcGU9MSwgYnI9MSkpIAogICAgbmxzKHkgfiBsYWdMaW5lKHg9eCwgbG93ZXIsIHNsb3BlLCBiciksCiAgICAgICAgc3RhcnQgPSBzdGFydF9saXN0LAogICAgICAgIGFsZ29yaXRobT0icG9ydCIsCiAgICAgICAgY29udHJvbD1ubHMuY29udHJvbChtYXhpdGVyPTUwMCkKICAgICAgICApCmBgYAoKIyMjIyBGaXQgdGhlIGxhZy1saW5lYXIgbW9kZWwgdG8gZGF0YQpgYGB7ciBMYWctbGluZWFyIG1vZGVsIGZpdHMsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTd9CnBhcihtZnJvdz1jKDIsNSkpCmxhZ19saW5lYXJfbW9kZWxzIDwtIGxhcHBseShjZWxsX2xpbmVzLCBmdW5jdGlvbihjbCkgewogICAgbSA8LSB0cnlDYXRjaCh7CiAgICAgICAgZGF0IDwtIHN0YXRpY1tzdGF0aWMkQ2VsbF9MaW5lPT1jbCxdCiAgICAgICAgY3VsdHVyZV90eXBlIDwtIHVuaXF1ZShkYXQkQ3VsdHVyZV9UeXBlKQogICAgICAgIG0gPC0gZml0TGFnTGluKGxvZzIoZGF0JENlbGxfQ29uYyksIGxvZzIoZGF0JFJMVSkpCiAgICB9LCBlcnJvcj1mdW5jdGlvbihlKSB7IHJldHVybihlKSB9KQogICAgIyBpZihjdWx0dXJlX3R5cGUgPT0gIkFkaGVyZW50IikgCiAgICAjIHsKICAgICMgICAgIHhyIDwtIGMoNCwxMikKICAgICMgfSBlbHNlIHsKICAgICMgICAgIHhyIDwtIGMoNiwxNCkKICAgICMgfQogICAgeHIgPC0gYygwLDE0KQogICAgcGxvdChsb2cyKFJMVSkgfiBsb2cyKENlbGxfQ29uYyksIGRhdGE9ZGF0LCBtYWluPXBhc3RlMChjbCwiICgiLGN1bHR1cmVfdHlwZSwiKSIpLCB4bGltPXhyLCB5bGltPWMoNywxOCkpCiAgICBpZihjbGFzcyhtKVsxXSAhPSAibmxzIikKICAgIHsKICAgICAgICBtIDwtIGxtKGxvZzIoUkxVKSB+IGxvZzIoQ2VsbF9Db25jKSwgZGF0YT1kYXRbZGF0JENlbGxfQ29uYz4xLF0pCiAgICAgICAgYWJsaW5lKG0sIGNvbD0iYmx1ZSIsIGx3ZD0yKQogICAgfSBlbHNlIHsKICAgICAgICBjdXJ2ZShmcm9tPTAuNSx0bz0xOCwgbGFnTGluZSh4LCBsb3dlcj1jb2VmKG0pWydsb3dlciddLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbG9wZT1jb2VmKG0pWydzbG9wZSddLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicj1jb2VmKG0pWydiciddKSwgCiAgICAgICAgICAgICAgY29sPSJibHVlIiwgbHdkPTIsIGFkZD1UUlVFKQogICAgfQogICAgIyB0ZXh0KDEyLCA5LCBwYXN0ZSgiQWRqIFIyID0iKSkKICAgIHJldHVybihtKQp9KQpuYW1lcyhsYWdfbGluZWFyX21vZGVscykgPC0gY2VsbF9saW5lcwpgYGAKCgojIyMjIFRyeSBlbGltaW5hdGluZyBjb250cm9scwpBc3N1bWUgYWxsIGx1bWluZXNjZW5jZSB2YWx1ZXMgd2l0aCBjZWxscyBwcm9kdWNlIGRldGVjdGFibGUgc2lnbmFsLgpgYGB7ciBDZWxsIGNvdW50ICYgbHVtaW5lc2NlbmNlIG1pbnVzIGNvbnRyb2wsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTd9CnBhcihtZnJvdz1jKDIsNSkpCmxpbmVhcl9tb2RlbHMgPC0gbGFwcGx5KGNlbGxfbGluZXMsIGZ1bmN0aW9uKGNsKSB7CiAgICAgICAgZGF0IDwtIHN0YXRpY1tzdGF0aWMkQ2VsbF9MaW5lPT1jbCAmIHN0YXRpYyRDZWxsX0NvbmMgPjEsXQogICAgICAgIGN1bHR1cmVfdHlwZSA8LSB1bmlxdWUoZGF0JEN1bHR1cmVfVHlwZSkKICAgICAgICBtIDwtIGxtKGxvZzIoUkxVKSB+IGxvZzIoQ2VsbF9Db25jKSwgZGF0YSA9IGRhdCkKICAgICAgICBwbG90KGxvZzIoUkxVKSB+IGxvZzIoQ2VsbF9Db25jKSwgZGF0YT1kYXQsIG1haW49cGFzdGUwKGNsLCIgKCIsY3VsdHVyZV90eXBlLCIpIiksIHhsaW09YygwLDE0KSwgeWxpbT1jKDcsMTgpKQogICAgICAgIGFibGluZShtLCBjb2w9ImJsdWUiKQogICAgICAgIHJldHVybihtKQogICAgfSkKbmFtZXMobGluZWFyX21vZGVscykgPC0gY2VsbF9saW5lcwpgYGAKCgoKIyMjIyBDb21wYXJlIHRvIGxpbmVhciBtb2RlbHMKQXNzdW1pbmcgdGhlIGxvd2VzdCBudW1iZXIgb2YgY2VsbHMgaXMgYWJvdmUgdGhlIHRocmVzaG9sZCBvZiBkZXRlY3Rpb24sIHdpbGwgcmVtb3ZlIHRoZSBubyBjZWxscyBjb250cm9sIGFuZCBmaXQgcmVtYWluaW5nIGRhdGEuCmBgYHtyfQpkYXQgPC0gc3RhdGljW3N0YXRpYyRDZWxsX0NvbmMgPiAxLF0KbTIgPC0gbG1lNDo6bG1MaXN0KGxvZzIoUkxVKSB+IGxvZzIoQ2VsbF9Db25jKSB8IENlbGxfTGluZSwgZGF0YT1kYXQpCmYyIDwtIGNvZWYobTIpCnIyIDwtIHVubGlzdChzdW1tYXJ5KG0yKSRhZGouci5zcXVhcmVkKQpgYGAKCmBgYHtyIExpbmVhciBtb2RlbHMsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTd9CnBhcihtZnJvdz1jKDIsNSkpCnRlbXAgPC0gbGFwcGx5KGNlbGxfbGluZXMsIGZ1bmN0aW9uKGNsKSB7CiAgICBkdHAgPC0gZGF0W2RhdCRDZWxsX0xpbmU9PWNsLF0KICAgIGN1bHR1cmVfdHlwZSA8LSB1bmlxdWUoZHRwW2R0cCRDZWxsX0xpbmU9PWNsLCdDdWx0dXJlX1R5cGUnXSkKICAgIGlmKGN1bHR1cmVfdHlwZSA9PSAiQWRoZXJlbnQiKSAKICAgIHsKICAgICAgICB4ciA8LSBjKDUsMTIpCiAgICB9IGVsc2UgewogICAgICAgIHhyIDwtIGMoNywxNCkKICAgIH0KICAgIHBsb3QobG9nMihSTFUpIH4gbG9nMihDZWxsX0NvbmMpLCAKICAgICAgICAgZGF0YT1kdHAsIAogICAgICAgICBtYWluPXBhc3RlMChjbCwiICgiLGN1bHR1cmVfdHlwZSwiKSIpLCAKICAgICAgICAgeGxpbT14ciwgeWxpbT1jKDcsMTgpKQogICAgYWJsaW5lKG0yW1tjbF1dLCBjb2w9ImJsdWUiLCBsd2Q9MikKICAgIHRleHQoeHJbMV0rMC4yNSwgMTcsIHBvcz00LCBwYXN0ZSgic2xvcGUgPSIsc2lnbmlmKGYyW2NsLDJdLDMpKSkKICAgIHRleHQoeHJbMV0rMC4yNSwgMTYsIHBvcz00LCBleHByZXNzaW9uKFJeMikpCiAgICB0ZXh0KHhyWzFdKzEsIDE2LCBwb3M9NCwgcGFzdGUoIj0iLCBzaWduaWYocjJbY2xdLDMpKSkKfSkKCmBgYAoKYGBge3IgTGFnLWxpbmVhciBzbG9wZSBlcSAxIG1vZGVsIGZpdHMsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTd9CmZpdExhZ0xpbjEgPC0gZnVuY3Rpb24oeCwgeSwgc3RhcnRfbGlzdD1saXN0KGxvd2VyPTUsIGJyPTEpKSAKICAgIG5scyh5IH4gbGFnTGluZSh4PXgsIGxvd2VyLCBzbG9wZT0xLCBiciksCiAgICAgICAgc3RhcnQgPSBzdGFydF9saXN0LAogICAgICAgIGFsZ29yaXRobT0icG9ydCIsCiAgICAgICAgY29udHJvbD1ubHMuY29udHJvbChtYXhpdGVyPTUwMCkKICAgICAgICApCgpwYXIobWZyb3c9YygyLDUpKQpsYWdfbGluZWFyMV9tb2RlbHMgPC0gbGFwcGx5KGNlbGxfbGluZXMsIGZ1bmN0aW9uKGNsKSB7CiAgICBtIDwtIHRyeUNhdGNoKHsKICAgICAgICBkYXQgPC0gc3RhdGljW3N0YXRpYyRDZWxsX0xpbmU9PWNsLF0KICAgICAgICBjdWx0dXJlX3R5cGUgPC0gdW5pcXVlKGRhdCRDdWx0dXJlX1R5cGUpCiAgICAgICAgbSA8LSBmaXRMYWdMaW4xKGxvZzIoZGF0JENlbGxfQ29uYyksIGxvZzIoZGF0JFJMVSkpCiAgICB9LCBlcnJvcj1mdW5jdGlvbihlKSB7IHJldHVybihlKSB9KQogICAgIyBpZihjdWx0dXJlX3R5cGUgPT0gIkFkaGVyZW50IikgCiAgICAjIHsKICAgICMgICAgIHhyIDwtIGMoNCwxMikKICAgICMgfSBlbHNlIHsKICAgICMgICAgIHhyIDwtIGMoNiwxNCkKICAgICMgfQogICAgeHIgPC0gYygwLDE0KQogICAgcGxvdChsb2cyKFJMVSkgfiBsb2cyKENlbGxfQ29uYyksIGRhdGE9ZGF0LCBtYWluPXBhc3RlMChjbCwiICgiLGN1bHR1cmVfdHlwZSwiKSIpLCB4bGltPXhyLCB5bGltPWMoNywxOCkpCiAgICBpZihjbGFzcyhtKVsxXSAhPSAibmxzIikKICAgIHsKICAgICAgICBtIDwtIGxtKGxvZzIoUkxVKSB+IGxvZzIoQ2VsbF9Db25jKSwgZGF0YT1kYXRbZGF0JENlbGxfQ29uYz4xLF0pCiAgICAgICAgYWJsaW5lKG0sIGNvbD0iYmx1ZSIsIGx3ZD0yKQogICAgfSBlbHNlIHsKICAgICAgICBjdXJ2ZShmcm9tPTAuNSx0bz0xOCwgbGFnTGluZSh4LCBsb3dlcj1jb2VmKG0pWydsb3dlciddLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbG9wZT0xLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicj1jb2VmKG0pWydiciddKSwgCiAgICAgICAgICAgICAgY29sPSJibHVlIiwgbHdkPTIsIGFkZD1UUlVFKQogICAgfQogICAgIyB0ZXh0KDEyLCA5LCBwYXN0ZSgiQWRqIFIyID0iKSkKICAgIHJldHVybihtKQp9KQpuYW1lcyhsYWdfbGluZWFyMV9tb2RlbHMpIDwtIGNlbGxfbGluZXMKYGBgCgoKCiMjIENvbWJpbmVkIGNlbGwgY291bnQgJiBsdW0gZGF0YQoKYGBge3J9CmxjYyA8LSByZWFkLmNzdignLi4vZGF0YS9mcm9tX0NXX3ZpYV9TbGFja18yMDIxMDUwNy8yMDIwMTIxNl9MdW1fQ2VsbENvdW50c19UaHVub3IuY3N2Jywgcm93Lm5hbWVzPTEsIGFzLmlzPVRSVUUpCmxjYyA8LSBsY2NbLC0xXQpsY2MkdWlkIDwtIHBhc3RlKGxjYyR1cGlkLGxjYyR3ZWxsLHNlcD0iXyIpCmxjYyA8LSBsY2Nbb3JkZXIobGNjJHVpZCxsY2MkdGltZSksXQpsY2MgPC0gbGNjW2xjYyR0aW1lIDw9IDk2LF0KYGBgCgoKYGBge3J9CmxjY19jZWxsX2xpbmVzIDwtIHVuaXF1ZShsY2MkY2VsbC5saW5lKQpjdHJscyA8LSBsYXBwbHkobGNjX2NlbGxfbGluZXMsIGZ1bmN0aW9uKGNsKSBsY2NbbGNjJGNlbGwubGluZT09Y2wgJiBsY2MkZHJ1ZzEuY29uYz09MCxdKQpuYW1lcyhjdHJscykgPC0gbGNjX2NlbGxfbGluZXMKYGBgCgojIyMjIENlbGwgY291bnRzCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTZ9CnBhcihtZnJvdz1jKDIsMikpCmludmlzaWJsZShsYXBwbHkobmFtZXMoY3RybHMpLCBmdW5jdGlvbihuKSBkby5jYWxsKHBsb3RHQywgCiAgICAgICAgYXBwZW5kKGdldEdDYXJncyhjdHJsc1tbbl1dLCBkYXQuY29sPWMoInRpbWUiLCJDZWxsX0NvdW50IiwidWlkIikpLGxpc3QobWFpbj1uLCBsZWc9RkFMU0UpKSkpKQpgYGAKIyMjIyBMdW1pbnNjZW5jZQpgYGB7ciBMdW1pbmVzY2VuY2UsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTZ9CnBhcihtZnJvdz1jKDIsMikpCmludmlzaWJsZShsYXBwbHkobmFtZXMoY3RybHMpLCBmdW5jdGlvbihuKSBkby5jYWxsKHBsb3RHQywgCiAgICAgICAgYXBwZW5kKGdldEdDYXJncyhjdHJsc1tbbl1dLCBkYXQuY29sPWMoInRpbWUiLCJSTFUiLCJ1aWQiKSksbGlzdChtYWluPW4sIGxlZz1GQUxTRSkpKSkpCmBgYAoKIyMjIyBTdW0gb2YgYWxsIGNvbnRyb2wgY2VsbCBjb3VudHMgYXQgZWFjaCB0aW1lIHBvaW50CmBgYHtyfQpzdW1jIDwtIGRvLmNhbGwocmJpbmQsIGxhcHBseShsY2NfY2VsbF9saW5lcywgZnVuY3Rpb24oY2wpIAogICAgewogICAgICAgIGNvdW50cyA8LSBzYXBwbHkodW5pcXVlKGN0cmxzW1tjbF1dJHRpbWUpLCBmdW5jdGlvbihpKSAKICAgICAgICAgICAgc3VtKGN0cmxzW1tjbF1dW2N0cmxzW1tjbF1dJHRpbWU9PWksIkNlbGxfQ291bnQiXSkpCiAgICAgICAgdGltZXMgPC0gdW5pcXVlKGN0cmxzW1tjbF1dJHRpbWUpCiAgICAgICAgZGF0YS5mcmFtZShjZWxsLmxpbmU9Y2wsIHRpbWU9dGltZXMsIGNlbGwuY291bnQ9Y291bnRzKQp9KSkKc3VtYyRwb3BkdWJzIDwtIGxvZzJub3JtKHN1bWMkY2VsbC5jb3VudCxzdW1jJGNlbGwubGluZSkKc3VtYyRjZWxsLmxpbmUgPC0gYXMuY2hhcmFjdGVyKHN1bWMkY2VsbC5saW5lKQojIGgxMDQ4X3N1bWMgPC0gZGF0YS5mcmFtZSh0aW1lPXVuaXF1ZShoMTA0OCR0aW1lKSwgY2VsbC5jb3VudD1oMTA0OF9zdW1jKQojIHBsb3QobG9nMihjZWxsLmNvdW50KS1sb2cyKGNlbGwuY291bnQpWzFdIH4gdGltZSwgZGF0YT1oMTA0OF9zdW1jLCB0eXBlPSJsIiwgeWxhYj0iUG9wdWxhdGlvbiBkb3VibGluZ3MiKQpgYGAKCgpgYGB7ciBETVM1MywgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9Nn0KcGFyKG1mcm93PWMoMywyKSkKCmludmlzaWJsZShsYXBwbHkobGNjX2NlbGxfbGluZXNbbGNjX2NlbGxfbGluZXMhPSJXTTg4Il0sIGZ1bmN0aW9uKGNsKQp7CiAgICBkdHAgPC0gY3RybHNbW2NsXV0KICAgIGludmlzaWJsZShkby5jYWxsKHBsb3RHQywgYXBwZW5kKGdldEdDYXJncyhkdHAsIGRhdC5jb2w9YygidGltZSIsIkNlbGxfQ291bnQiLCJ1aWQiKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KG1haW49cGFzdGUwKGNsLCIsIGNlbGwgY291bnQiKSwgbGVnPUZBTFNFKSkpKQogICAgaW52aXNpYmxlKGRvLmNhbGwocGxvdEdDLCBhcHBlbmQoZ2V0R0NhcmdzKGR0cCwgZGF0LmNvbD1jKCJ0aW1lIiwiUkxVIiwidWlkIikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdChtYWluPXBhc3RlMChjbCwiLCBsdW0iKSwgbGVnPUZBTFNFLCB5bGltPWMoLTEsMykpKSkpCiAgICBsaW5lcyhzdW1jW3N1bWMkY2VsbC5saW5lPT1jbCwidGltZSJdLCBzdW1jW3N1bWMkY2VsbC5saW5lPT1jbCwicG9wZHVicyJdLCBsd2Q9MykKfSkpCgpgYGAKCgoKYGBge3IgTHVtLW9ubHl9Cmx1bW8gPC0gcmVhZC5jc3YoIi4uL2RhdGEvZnJvbV9DV192aWFfU2xhY2tfMjAyMTA1MDcvMDUwODE5X1JUZ2xvd0RGLmNzdiIpCmx1bW8gPC0gbHVtb1ssLTFdCgpsdW1vX2NlbGxfbGluZXMgPC0gdW5pcXVlKGx1bW8kY2VsbC5saW5lKQpsdW1vX2N0cmxfZGF0IDwtIGxhcHBseShsdW1vX2NlbGxfbGluZXMsIGZ1bmN0aW9uKGNsKSBsdW1vW2x1bW8kY2VsbC5saW5lPT1jbCAmIGx1bW8kZHJ1ZzEuY29uYz09MCAmIGx1bW8kZHJ1ZzEhPSJETVNPIixdKQpuYW1lcyhsdW1vX2N0cmxfZGF0KSA8LSBsdW1vX2NlbGxfbGluZXMKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04fQpwYXIobWZyb3c9YygyLDQpKQoKaW52aXNpYmxlKGxhcHBseShsdW1vX2NlbGxfbGluZXMsIGZ1bmN0aW9uKGNsKQp7CiAgICBkdHAgPC0gbHVtb19jdHJsX2RhdFtbY2xdXQogICAgIyBwbG90KGR0cCRUb3RIb3VyLCBkdHAkUkxVKQogICAgCiAgICBpbnZpc2libGUoZG8uY2FsbChwbG90R0MsIGFwcGVuZChnZXRHQ2FyZ3MoZHRwLCBkYXQuY29sPWMoIlRvdEhvdXIiLCJSTFUiLCJ3ZWxsIiksIGFyZy5uYW1lID0gYygidGltZSIsICJjZWxsLmNvdW50IiwgImlkcyIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QobWFpbj1jbCwgbGVnPUZBTFNFKSkpKQp9KSkKYGBgCgoKYGBge3J9CmNvcmwyNzkgPC0gbHVtb19jdHJsX2RhdFtbJ0NPUkwyNzknXV0KCmRvLmNhbGwocGxvdEdDLCBhcHBlbmQoZ2V0R0NhcmdzKGEsIGRhdC5jb2w9YygiVG90SG91ciIsIlJMVSIsIndlbGwiKSwgYXJnLm5hbWUgPSBjKCJ0aW1lIiwgImNlbGwuY291bnQiLCAiaWRzIikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdChtYWluPSJDT1JMMjc5IiwgbGVnPUZBTFNFKSkpCmBgYAoK